package spider.netty.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spider.netty.client.handler.ClientListener;
import spider.netty.client.handler.WebSocketClientHandler;
import spider.zookeeper.util.StringUtils;

import java.net.URI;

/**
 * This is an example of a WebSocket client.
 * <p/>
 * In order to run this example you need a compatible WebSocket server.
 * Therefore you can either start the WebSocket server
 * or connect to an existing WebSocket server such as
 * <a href="http://www.websocket.org/echo.html">ws://echo.websocket.org</a>.
 * <p/>
 * The client will attempt to connect to the URI passed to it as the first argument.
 * You don't have to specify any arguments if you want to connect to the example WebSocket server,
 * as this is the default.
 */

public class WebSocketClient {
    private static Logger logger = LoggerFactory.getLogger(WebSocketClient.class);
    private URI uri;
    private String host;
    private int port;
    private boolean ssl;
    private EventLoopGroup group;

    private void init(String url) {
        try {
            uri = new URI(url);
            String scheme = uri.getScheme() == null ? "http" : uri.getScheme();
            if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
                logger.error("Only WS(S) is supported.");
                return;
            }
            host = uri.getHost() == null ? "127.0.0.1" : uri.getHost();
            if (uri.getPort() == -1) {
                if ("http".equalsIgnoreCase(scheme)) {
                    port = 80;
                } else if ("https".equalsIgnoreCase(scheme)) {
                    port = 443;
                } else {
                    port = -1;
                }
            } else {
                port = uri.getPort();
            }
            ssl = "wss".equalsIgnoreCase(scheme);
            uri = new URI(scheme + "://" + host + ":" + port  + uri.getPath());
        } catch (Exception e) {
            logger.error(StringUtils.printStackTrace(e));
        }
    }

    public Channel establish(String url, ClientListener listener) {
        Channel ch = null;
        group = new NioEventLoopGroup();
        try {
            init(url);
            final SslContext sslCtx;
            if (ssl) {
                //sslCtx =  SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE);
                sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
            } else {
                sslCtx = null;
            }

            final WebSocketClientHandler handler = new WebSocketClientHandler(WebSocketClientHandshakerFactory
                                                                                      .newHandshaker(uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders()));
            handler.setClientListener(listener);
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    ChannelPipeline p = ch.pipeline();
                    if (sslCtx != null) {
                        p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
                    }
                    p.addLast(new HttpClientCodec(), new HttpObjectAggregator(8192), handler);
                }
            });
            ch = b.connect(uri.getHost(), port).sync().channel();
            handler.handshakeFuture().sync();
        } catch (Exception e) {
            logger.error(StringUtils.printStackTrace(e));
            group.shutdownGracefully();
        }
        return ch;
    }

    public void close() {
        if (group != null) {
            group.shutdownGracefully();
        }
    }
}